use anyhow::Result;
use retour::static_detour;
use std::collections::HashSet;
use std::ffi::{c_char, c_void};
use std::ops::Deref;
use std::sync::Mutex;

use crate::types::ColorPair;
use crate::*;

hook! {
  fn addst(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32);
  fn addst_flag(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32, sflag: u32);
  fn addcoloredst(gps_ptr: *const c_void, string_ptr: *const c_char, color_string_ptr: *const c_char);
  fn top_addst(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32);
  // TODO: draw_nineslice
  // TODO: draw_horizontal_nineslice
  // TODO: gps_allocate
  fn update_all(renderer_ptr: *const c_void);
  // TODO: update_tile
  // TODO: get_key_display
  fn mtb_process_string_to_lines(mtb_ptr: *const c_void, markup_ptr: *const c_void);
  // TODO: mtb_set_width
  // TODO: render_help_dialog
}

fn addst(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32) {
  let bt = utils::backtrace();

  let string = strings::cxx_string_to_string(string_ptr);
  log_text("addst", &bt, &string);

  // call_addst(gps_ptr, string_ptr, just, space);
}

fn addst_flag(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32, sflag: u32) {
  let bt = utils::backtrace();

  let string = strings::cxx_string_to_string(string_ptr);
  log_text("addst_flag", &bt, &string);

  // call_addst_flag(gps_ptr, string_ptr, just, space, sflag);
}

fn addcoloredst(gps_ptr: *const c_void, string_ptr: *const c_char, color_string_ptr: *const c_char) {
  let bt = utils::backtrace();

  let string = strings::c_string_to_string(string_ptr);
  log_text("addcoloredst", &bt, &string);

  // call_addcoloredst(gps_ptr, string_ptr, color_string_ptr);
}

fn top_addst(gps_ptr: *const c_void, string_ptr: *const c_void, just: u8, space: i32) {
  let bt = utils::backtrace();

  let string = strings::cxx_string_to_string(string_ptr);
  log_text("top_addst", &bt, &string);

  // call_top_addst(gps_ptr, string_ptr, just, space);
}

// TODO: draw_nineslice
// TODO: draw_horizontal_nineslice
// TODO: gps_allocate

#[static_init::dynamic]
static mut DIMENSIONS: types::Dimensions = Default::default();

fn update_all(renderer_ptr: *const c_void) {
  let mut dimensions = DIMENSIONS.write();

  let last_dimensions = dimensions.deref();
  let curr_dimensions = game::global_gps_get_dimensions();
  if curr_dimensions != last_dimensions {
    dimensions.clone_from(curr_dimensions);
    err!("Dimension changed: {curr_dimensions:?}");
  }

  call_update_all(renderer_ptr);
  if unsafe { i18n_enabled } {
    // TODO: render
  }
  do_toggle();
}

// TODO: update_tile
// TODO: get_key_display

fn mtb_process_string_to_lines(mtb_ptr: *const c_void, markup_ptr: *const c_void) {
  let bt = utils::backtrace();
  let markup = strings::cxx_string_to_string(markup_ptr);
  log_text("mtb_process_string_to_lines", &bt, &markup);

  call_mtb_process_string_to_lines(mtb_ptr, markup_ptr);
}

// TODO: mtb_set_width
// TODO: render_help_dialog

unsafe extern "C" {
  fn i18n_get_screen() -> *const c_void;
}

fn get_screen() -> String {
  let screen_ptr = unsafe { i18n_get_screen() };
  strings::cxx_string_to_string(screen_ptr)
}

#[static_init::dynamic]
static mut VISITED: HashSet<String> = HashSet::new();

fn log_text(function: &str, backtrace: &str, string: &str) {
  let coordinate = game::global_gps_get_coordinate();
  let color_info = game::global_gps_get_color_info();

  if string.len() < 2 {
    return;
  }

  if string.starts_with("FPS: ") {
    return;
  }

  if string.chars().all(|c| c.is_ascii_digit() || c.is_ascii_punctuation() || c.is_ascii_whitespace()) {
    return;
  }

  if VISITED.read().contains(string) {
    return;
  }

  VISITED.write().insert(string.to_string());
  err!("Untranslated text at {coordinate:?}");
  log!("{function}: {} @ {backtrace}", get_screen());
  log!("{:?}", ColorPair::from(color_info.clone()));
  log!("{string:?}");
}

unsafe extern "C" {
  static i18n_enabling: bool;
  static i18n_disabling: bool;
  static i18n_enabled: bool;
  fn i18n_set_enabled(enabled: bool);
}

pub fn attach_all() -> Result<()> {
  attach_addst()?;
  attach_addst_flag()?;
  attach_addcoloredst()?;
  attach_top_addst()?;
  // TODO: draw_nineslice
  // TODO: draw_horizontal_nineslice
  // TODO: gps_allocate
  attach_update_all()?;
  // TODO: update_tile
  // TODO: get_key_display
  attach_mtb_process_string_to_lines()?;
  // TODO: mtb_set_width
  // TODO: render_help_dialog

  // always enable update_all
  enable_update_all()?;
  // always enable mtb_process_string_to_lines
  enable_mtb_process_string_to_lines()?;

  set_enabled(true);

  Ok(())
}

fn enable_all() -> Result<()> {
  enable_addst()?;
  enable_addst_flag()?;
  enable_addcoloredst()?;
  enable_top_addst()?;
  // TODO: draw_nineslice
  // TODO: draw_horizontal_nineslice
  // TODO: gps_allocate
  // always enable update_all
  // TODO: update_tile
  // TODO: get_key_display
  // always enable mtb_process_string_to_lines
  // TODO: mtb_set_width
  // TODO: render_help_dialog

  unsafe { i18n_set_enabled(true) };

  Ok(())
}

fn disable_all() -> Result<()> {
  disable_addst()?;
  disable_addst_flag()?;
  disable_addcoloredst()?;
  disable_top_addst()?;
  // TODO: draw_nineslice
  // TODO: draw_horizontal_nineslice
  // TODO: gps_allocate
  // always enable update_all
  // TODO: update_tile
  // TODO: get_key_display
  // always enable mtb_process_string_to_lines
  // TODO: mtb_set_width
  // TODO: render_help_dialog

  unsafe { i18n_set_enabled(false) };

  Ok(())
}

static HOOK_LOCK: Mutex<()> = Mutex::new(());
pub fn set_enabled(enabled: bool) {
  let _lock = HOOK_LOCK.lock().expect("failed to lock hook mutex");

  if enabled {
    enable_all().expect("failed to enable hooks");
  } else {
    disable_all().expect("failed to disable hooks");
  }
}

pub fn do_toggle() {
  if unsafe { i18n_enabling } {
    set_enabled(true);
  }

  if unsafe { i18n_disabling } {
    set_enabled(false);
  }
}
